///////////////////////////////////////////////////////////////////////////////////////////////////
/// \file formic/fqmc/distance_proposer.h
///
/// \brief   header file for a class for proposing moves based on distances between orbitals
///
///////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef FORMIC_FQMC_DISTANCE_PROPOSER_HEADER
#define FORMIC_FQMC_DISTANCE_PROPOSER_HEADER

#include<vector>
#include<set>

#include<formic/random/random.h>
#include<formic/wfn/configuration.h>

namespace formic {

namespace fqmc {

  class InputBase;

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  /// \brief  A class for proposing moves based on distances between orbitals
  ///
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  class DistProposer {

    private:

      /// \brief  the number of orbitals
      int _norb;

      /// \brief  the number of alpha electrons
      int _na;

      /// \brief  the number of beta electrons
      int _nb;

      /// \brief  the number of irreducible representations
      int _nirr;

      /// \brief  variance of the distance-based normal distribution
      double _sig2;

      /// \brief  the probability of proposing a 2-electron (rather than 1-electron) move
      double _2e_prob;

      /// \brief  a configuration object used in proposal calculations
      formic::Configuration _conf;

      /// \brief  the direct product table for the symmetry point group
      std::vector<int> _dpt;

      /// \brief  the irreps of the orbitals
      std::vector<int> _orb_irrep;

      /// \brief  the coordinates of the orbitals
      std::vector<double> _orbc;

      /// \brief  the number of particles/holes/pairs in the list
      int _np;

      /// \brief  a list to hold particles, holes, and pairs
      std::vector<int> _plist;

      /// \brief  weights based on orbital distances
      std::vector<double> _dw;

      // variables for fast 2e proposal method
      int _n_all_aa_hole_pairs;
      int _n_all_bb_hole_pairs;
      int _n_all_ab_hole_pairs;
      std::vector<int> _all_aa_hole_pairs;
      std::vector<int> _all_bb_hole_pairs;
      std::vector<int> _all_ab_hole_pairs;
      std::vector<int> _aa_hole_pair_irrep_count;
      std::vector<int> _bb_hole_pair_irrep_count;
      std::vector<int> _ab_hole_pair_irrep_count;
      int _n_all_aa_part_pairs;
      int _n_all_bb_part_pairs;
      int _n_all_ab_part_pairs;
      std::vector<int> _all_aa_part_pairs;
      std::vector<int> _all_bb_part_pairs;
      std::vector<int> _all_ab_part_pairs;
      std::vector<int> _aa_part_pair_irrep_count;
      std::vector<int> _bb_part_pair_irrep_count;
      std::vector<int> _ab_part_pair_irrep_count;
      std::vector<int> _chosen_pair_irrep_counts;
      std::vector<double> _dw_mat;

    private:

      /// \brief  returns the irrep of the direct product of a pair of orbitals
      int orb_pair_dp(int x, int y) const {
        return _dpt[ _nirr*_orb_irrep[x] + _orb_irrep[y] ];
      }

      /// \brief  returns the irrep of the direct product of a pair of sites
      int site_pair_dp(int x, int y) const {
        return _dpt[ _nirr*_orb_irrep[x/2] + _orb_irrep[y/2] ];
      }

      void get_pairs(const int s1,
                     const int s2,
                     const int occ,
                     const std::set<int> & allwi);

      void get_distance_weights(const int o1);

      void get_distance_weights(const int o1, const int o2);

      void get_valid_particles(const int b = -1);

      void get_valid_holes(const int p1);

      void get_valid_particle_pairs();

      double propose_1e(const formic::InputBase & userinp,
                        const formic::Configuration & config,
                        formic::ConfigDiff & diff,
                        formic::LaggedFibonacci<> & lfg);

      double propose_2e(const formic::InputBase & userinp,
                        const formic::Configuration & config,
                        formic::ConfigDiff & diff,
                        formic::LaggedFibonacci<> & lfg);

      double fast_propose_2e(const formic::InputBase & userinp,
                             const formic::Configuration & config,
                             formic::ConfigDiff & diff,
                             formic::LaggedFibonacci<> & lfg);

      void prep_pair_array_and_irrep_counts_aa(const int norb,
                                               const int nirr,
                                               const int * const dpt,
                                               const int * const orbirr,
                                               const int * const orbs,
                                               int & npair,
                                               int * const pairs,
                                               int * const irrcnt);

      void prep_pair_array_and_irrep_counts_ab(const int na,
                                               const int nb,
                                               const int nirr,
                                               const int * const dpt,
                                               const int * const orbirr,
                                               const int * const orba,
                                               const int * const orbb,
                                               int & npair,
                                               int * const pairs,
                                               int * const irrcnt);

      void prep_pairs_and_irreps();

      int get_irrep_allowed_pairs(const int s1,
                                  const int s2,
                                  const int npair,
                                  const int nirr,
                                  const int * const pairs,
                                  const int * const irrcnt,
                                  int * const allowed);

      void fast_get_distance_weights(const int o1);
      void fast_get_distance_weights(const int o1, const int o2);

      void count_irreps(const int norb,
                        const int nirr,
                        const int * const orbirr,
                        const int * const orbs,
                        int * const irrcnt);

    public:

      DistProposer(const formic::InputBase & userinp);

      double propose(const formic::InputBase & userinp,
                     const formic::Configuration & config,
                     formic::ConfigDiff & diff,
                     formic::LaggedFibonacci<> & lfg);

  };

}

}

#endif
